﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Data;
using System.Text.RegularExpressions;

namespace CodeSnippetsEditor
{
    class SyntaxHighlightingRichTextBox : RichTextBox
    {
        #region Importovane funkce knihoven

        [DllImport("user32.dll")]
        public static extern int SendMessage(int hWnd, uint Msg, long wParam, long lParam);

        [DllImport("user32.dll")]
        public static extern int LockWindowUpdate(int hWnd);

        #endregion


        #region Promenne formulare

        private const uint LineIndex = 187;
        private const uint LineFromChar = 201;
        private const uint GetFirstVisibleLine = 206;
        private const uint CharFromPos = 215;
        private const uint PosFromChar = 1062;
        
        private bool syntaxHighlightCaseSensitive;
        private Color backColor = Color.LightGreen;
        private Color foreColor = Color.Blue;
        private string delimiter = "$";

        private List<string> Words = new List<string>();

        #endregion


        #region Kontruktory

        /// <summary>
        /// Initializes a new instance of the <see cref="SyntaxHighlightingRichTextBox"/> class.
        /// </summary>
        public SyntaxHighlightingRichTextBox()
        {
            this.AcceptsTab = true;

            Words.AddRange(new string[] {"abstract", "event", "new", "struct", "as", "explicit", "null", "switch",
                "base", "extern", "object", "this", "bool", "false", "operator", "throw", "break", "finally", "out", "true",
                "byte", "fixed", "override", "try", "case", "float", "params", "typeof", "catch", "for", "private", "uint",
                "char", "foreach", "protected", "ulong", "checked", "goto", "public", "unchecked", "class", "if", "readonly",
                "const", "implicit", "ref", "ushort", "unsafe", "continue", "in", "return", "using", "decimal", "int", "sbyte", 
                "default", "interface", "sealed", "volatile", "virtual", "delegate", "internal", "short", "void", "do", "is", 
                "sizeof", "while", "double", "lock", "stackalloc", "else", "long", "static", "enum", "namespace", "string"});
        }

        #endregion


        #region Vlastnosti

        /// <summary>
        /// Gets or sets a value indicating whether [case sensitive].
        /// </summary>
        /// <value><c>true</c> if [case sensitive]; otherwise, <c>false</c>.</value>
        public bool CaseSensitive
        {
          get 
          { 
              return syntaxHighlightCaseSensitive; 
          }
          set 
          { 
              syntaxHighlightCaseSensitive = value; 
          }
        }

        /// <summary>
        /// Gets or sets the color of the placeholder back.
        /// </summary>
        /// <value>The color of the placeholder back.</value>
        public Color PlaceholderBackColor
        {
            get
            {
                return backColor;
            }
            set
            {
                backColor = value;
            }
        }

        /// <summary>
        /// Gets or sets the color of the syntax fore.
        /// </summary>
        /// <value>The color of the syntax fore.</value>
        public Color SyntaxForeColor
        {
            get
            {
                return foreColor;
            }
            set
            {
                foreColor = value;
            }
        }

        /// <summary>
        /// Gets or sets the delimiter.
        /// </summary>
        /// <value>The delimiter.</value>
        public string Delimiter
        {
            get
            {
                return delimiter;
            }
            set
            {
                delimiter = value;
            }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is select text placeholder.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is select text placeholder; otherwise, <c>false</c>.
        /// </value>
        public bool IsSelectionPlaceholder
        {
            get
            {
                return this.SelectionBackColor == backColor;
            }
        }

        #endregion


        #region Pretizene funkce a udalosti

        /// <summary>
        /// </summary>
        /// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.</param>
        protected override void OnTextChanged(EventArgs e)
        {
            this.Modified = false;
            ColorVisibleLines();
            this.Modified = false;
        }

        #endregion


        #region Verejne funkce

        /// <summary>
        /// Colors the RTB.
        /// </summary>
        public void ColorRtb()
        {
            int firstVisibleChar;
            int i = 0;

            while (i < this.Lines.Length)
	        {
	            firstVisibleChar = GetCharFromLineIndex(i);
                ColorLineNumber(i, firstVisibleChar);

                i++;
	        }
        }

        /// <summary>
        /// Colors the visible lines.
        /// </summary>
        public void ColorVisibleLines()
        {
            int firstLine = FirstVisibleLine();
            int lastLine = LastVisibleLine();
            int firstVisibleChar;
            int originCursorPosition = this.SelectionStart;

            //lock the update
            LockWindowUpdate(this.Handle.ToInt32());

            this.SelectAll();
            this.ColorSelection(Color.Black, Color.White, FontStyle.Regular);

            //unlock the update
            LockWindowUpdate(0);

            if(firstLine == 0 && lastLine == 0)
                return;
            else
            {
                while (firstLine < lastLine)
	            {
            	    firstVisibleChar = GetCharFromLineIndex(firstLine);
                    ColorLineNumber(firstLine, firstVisibleChar);

                    firstLine++;
	            }
            }

            //set origin position
            this.SelectionStart = originCursorPosition;
        }

        /// <summary>
        /// Colors the line number.
        /// </summary>
        /// <param name="LineIndex">Index of the line.</param>
        /// <param name="lStart">The l start.</param>
        public void ColorLineNumber(int LineIndex, int lStart)
        {
            int selectionAt = this.SelectionStart;
            string[] line;
            int myI;

            //lock the update
            LockWindowUpdate(this.Handle.ToInt32());
            
            myI = lStart;

            char[] separatedChar = new char[] { ' ', '.', ',', ';', ':', '=', '@', '-','(', ')', '[', ']', '{', '}' };

            if (CaseSensitive)
                line = this.Lines[LineIndex].ToString().Split(separatedChar, StringSplitOptions.None);
            else
                line = this.Lines[LineIndex].ToLower().Split(separatedChar, StringSplitOptions.None);

            foreach (string myStr in line)
	        {
        		this.SelectionStart = myI;
                this.SelectionLength = myStr.Length;

                if(myStr.Length > 2 && myStr.StartsWith(delimiter) && myStr.EndsWith(delimiter))
                {
                    ColorSelection(Color.Black, backColor, FontStyle.Italic);
                }
                else if (Words.Contains(myStr))
                {
                    if (!CaseSensitive || (CaseSensitive /*&&*/ )) //TODO: dodelat CaseSenzitive
                    {
                        ColorSelection(foreColor, Color.White, FontStyle.Bold);
                    }
                    else
                    {
                        ColorSelection(Color.Black, Color.White, FontStyle.Regular);
                    }
                }
                else
                {
                    ColorSelection(Color.Black, Color.White, FontStyle.Regular);
                }

                myI += myStr.Length + 1;
	        }

            //restore the selection start
            this.SelectionStart = selectionAt;
            this.SelectionLength = 0;
            this.SelectionColor = Color.Black;

            //unlock the update
            LockWindowUpdate(0);
        }

        /// <summary>
        /// Colors the selection.
        /// </summary>
        public void ColorSelection(Color foreColor, Color backColor, FontStyle style)
        {
            this.SelectionBackColor = backColor;
            this.SelectionColor = foreColor;
            this.SelectionFont = new Font("Courier New", (float)8.25, style);
        }

        /// <summary>
        /// Gets the index of the char from line.
        /// </summary>
        /// <param name="lineIndex">Index of the line.</param>
        /// <returns></returns>
        public int GetCharFromLineIndex(int lineIndex)
        {
            return SendMessage(this.Handle.ToInt32(), LineIndex, lineIndex, 0);
        }

        /// <summary>
        /// Firsts the visible line.
        /// </summary>
        /// <returns></returns>
        public int FirstVisibleLine()
        {
            return SendMessage(this.Handle.ToInt32(), GetFirstVisibleLine, 0, 0);
        }

        /// <summary>
        /// Lasts the visible line.
        /// </summary>
        /// <returns></returns>
        public int LastVisibleLine()
        {
            int lastLine = FirstVisibleLine() + (this.Height / this.Font.Height);

            if(lastLine > this.Lines.Length || lastLine == 0)
                lastLine = this.Lines.Length;

            return lastLine;
        }

        /// <summary>
        /// Gets the word.
        /// </summary>
        /// <returns></returns>
        public string GetWord()
        {
            string line = this.Lines[this.GetLineFromCharIndex(this.SelectionStart)];
            string pattern = "[a-zA-Z0-9" + delimiter + "]+";
            int index = this.SelectionStart - this.GetFirstCharIndexOfCurrentLine();

            Regex reg = new Regex(pattern);

            foreach (Match item in reg.Matches(line))
            {
                if (item.Index <= index && index <= (item.Index + item.Length))
                    return item.Value;
            }

            return string.Empty;
        }

        #endregion
    }
}
